{{!

  Copyright (c) Meta Platforms, Inc. and affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}{{!

Python wrappers of the structs defined in the services files. This file is
compiled into it's own module to be included by clients and services and
end-user code. It's one of the more complicated files, as it has to map
Pythonic APIs to C++ objects and back.

One of the nastier things in this file is the definition of containers.
A separate container wrapper has to be defined for each type of contained
attribute because Cython can't template C++ classes. So, for example, we need
a List__int16 or a List__string or a Map__string_mystruct instance for each
container/type combination. Consider that containers can contain other containers
or structs that contain containers and you realize how messy this can get.
Further, we'd prefer to have the end user freed from having to know about these
container types, so we'll need to define factories for them based on what they
want to include.

}}
{{> common/auto_generated_py}}
{{^program:auto_migrate?}}
cimport cython as __cython
from cpython.object cimport PyTypeObject
from libcpp.memory cimport shared_ptr, make_shared, unique_ptr
from libcpp.optional cimport optional as __optional
from libcpp.string cimport string
from libcpp cimport bool as cbool
from libcpp.iterator cimport inserter as cinserter
from libcpp.utility cimport move as cmove
from cpython cimport bool as pbool
from cython.operator cimport dereference as deref, preincrement as inc, address as ptr_address
import thrift.py3.types
from thrift.py3.types import _IsSet as _fbthrift_IsSet
from thrift.py3.types cimport make_unique
cimport thrift.py3.types
cimport thrift.py3.exceptions
cimport thrift.python.exceptions
import thrift.python.converter
from thrift.python.types import EnumMeta as __EnumMeta
from thrift.python.std_libcpp cimport sv_to_str as __sv_to_str, string_view as __cstring_view
from thrift.python.types cimport BadEnum as __BadEnum
from thrift.py3.types cimport (
    richcmp as __richcmp,
    init_unicode_from_cpp as __init_unicode_from_cpp,
    set_iter as __set_iter,
    map_iter as __map_iter,
    {{#program:python_capi_converter?}}
    py3_to_python as __py3_to_python,
    python_to_py3 as __python_to_py3,
    {{/program:python_capi_converter?}}
    reference_shared_ptr as __reference_shared_ptr,
    get_field_name_by_index as __get_field_name_by_index,
    reset_field as __reset_field,
    translate_cpp_enum_to_python,
    const_pointer_cast,
    make_const_shared,
    constant_shared_ptr,
    {{#program:inplace_migrate?}}
    deref_const as __deref_const,
    {{/program:inplace_migrate?}}
)
from thrift.py3.types cimport _ensure_py3_or_raise, _ensure_py3_container_or_raise
cimport thrift.py3.serializer as serializer
from thrift.python.protocol cimport Protocol as __Protocol
import folly.iobuf as _fbthrift_iobuf
from folly.optional cimport cOptional
from folly.memory cimport to_shared_ptr as __to_shared_ptr
from folly.range cimport Range as __cRange

import sys
from collections.abc import Sequence, Set, Mapping, Iterable
import weakref as __weakref
import builtins as _builtins
import importlib
{{#program:has_stream?}}
import asyncio
from folly.coro cimport bridgeCoroTaskWith
{{/program:has_stream?}}
{{#program:inplace_migrate?}}{{#program:needs_container_converters?}}
cimport {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.thrift_converter as _{{#program:py3Namespaces}}{{value}}_{{/program:py3Namespaces}}{{program:name}}_thrift_converter
{{/program:needs_container_converters?}}{{/program:inplace_migrate?}}
{{#program:includeNamespaces}}
{{#hasTypes?}}
cimport {{#includeNamespace}}{{value}}.{{/includeNamespace}}types as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}types
import {{#includeNamespace}}{{value}}.{{/includeNamespace}}types as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}types
{{#program:inplace_migrate?}}{{#program:needs_container_converters?}}
cimport {{#includeNamespace}}{{value}}.{{/includeNamespace}}thrift_converter as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}thrift_converter
{{/program:needs_container_converters?}}{{^program:needs_container_converters?}}{{#program:has_stream?}}
cimport {{#includeNamespace}}{{value}}.{{/includeNamespace}}thrift_converter as _{{#includeNamespace}}{{value}}_{{/includeNamespace}}thrift_converter
{{/program:has_stream?}}{{/program:needs_container_converters?}}{{/program:inplace_migrate?}}
{{/hasTypes?}}
{{/program:includeNamespaces}}

{{^program:inplace_migrate?}}
import {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.thrift_types as _fbthrift_python_types
{{#program:hasEnumTypes}}
from {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.thrift_enums import (
{{#program:enums}}
    {{enum:name}},
{{/program:enums}}
)
{{/program:hasEnumTypes}}
{{#program:hasUnionTypes}}
from {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.types_impl_FBTHRIFT_ONLY_DO_NOT_USE import (
{{#program:filtered_structs}}{{#struct:union?}}
    __{{struct:name}}Type,
{{/struct:union?}}{{/program:filtered_structs}}
)
{{/program:hasUnionTypes}}

{{#program:hasContainerTypes}}
from {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.containers_FBTHRIFT_ONLY_DO_NOT_USE import (
{{#program:containerTypes}}
    {{type:flat_name}},
{{/program:containerTypes}}
)
{{/program:hasContainerTypes}}
{{/program:inplace_migrate?}}
{{#program:inplace_migrate?}}

{{#program:has_stream?}}
cimport {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.thrift_converter as _{{#program:py3Namespaces}}{{value}}_{{/program:py3Namespaces}}{{program:name}}_thrift_converter
{{/program:has_stream?}}

import {{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.types_inplace_FBTHRIFT_ONLY_DO_NOT_USE as _fbthrift_types_inplace
{{#program:filtered_structs}}
{{struct:name}} = _fbthrift_types_inplace.{{struct:name}}
{{/program:filtered_structs}}
{{#program:enums}}
{{enum:name}} = _fbthrift_types_inplace.{{enum:name}}
{{/program:enums}}
{{#program:containerTypes}}
{{type:flat_name}} = _fbthrift_types_inplace.{{type:flat_name}}
{{/program:containerTypes}}
{{#program:constants}}{{#constant:value}}
{{constant:name}} = _fbthrift_types_inplace.{{constant:name}}
{{/constant:value}}{{/program:constants}}
{{/program:inplace_migrate?}}

{{^program:inplace_migrate?}}
_fbthrift__module_name__ = "{{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.types"

cdef object get_types_reflection():
    return importlib.import_module(
        "{{#program:py3Namespaces}}{{value}}.{{/program:py3Namespaces}}{{program:name}}.types_reflection"
    )

{{#program:filtered_structs}}
{{^struct:union?}}
@__cython.auto_pickle(False)
{{^struct:allow_inheritance?}}
@__cython.final
{{/struct:allow_inheritance?}}
cdef class {{struct:name}}({{> types/python_struct_class}}):
    __module__ = _fbthrift__module_name__

    def __init__({{struct:name}} self,{{#struct:exception?}} *args,{{/struct:exception?}} **kwargs):
        self.{{> types/cpp_obj}} = make_shared[{{> types/current_module_cbindings}}.{{> types/c_struct }}]()
        self._fields_setter = _fbthrift_types_fields.__{{struct:name}}_FieldsSetter._fbthrift_create(self.{{> types/cpp_obj}}.get())
        super().__init__({{#struct:exception?}} *args, {{/struct:exception?}}**kwargs)
    {{^struct:exception?}}
    {{^struct:cpp_noncopyable?}}

    def __call__({{struct:name}} self, **kwargs):
        {{^struct:py3_fields?}}
        return self
        {{/struct:py3_fields?}}
        {{#struct:py3_fields?}}
        if not kwargs:
            return self
        cdef {{struct:name}} __fbthrift_inst = {{struct:name}}.__new__({{struct:name}})
        __fbthrift_inst.{{> types/cpp_obj}} = make_shared[{{> types/current_module_cbindings}}.{{> types/c_struct }}](deref(self.{{> types/cpp_obj}}))
        __fbthrift_inst._fields_setter = _fbthrift_types_fields.__{{struct:name}}_FieldsSetter._fbthrift_create(__fbthrift_inst.{{> types/cpp_obj}}.get())
        for __fbthrift_name, _fbthrift_value in kwargs.items():
            (<thrift.py3.types.Struct>__fbthrift_inst)._fbthrift_set_field(__fbthrift_name, _fbthrift_value)
        return __fbthrift_inst
        {{/struct:py3_fields?}}
    {{/struct:cpp_noncopyable?}}
    {{/struct:exception?}}

    cdef void _fbthrift_set_field(self, str name, object value) except *:
        self._fields_setter.set_field(name.encode("utf-8"), value)

    cdef object _fbthrift_isset(self):
        return _fbthrift_IsSet("{{struct:name}}", {
        {{#struct:py3_fields}}
        {{#field:has_ref_accessor?}}
        {{^field:terse?}}
          "{{field:py_name}}": deref(self.{{> types/cpp_obj}}).{{field:py_name}}_ref().has_value(),
        {{/field:terse?}}
        {{/field:has_ref_accessor?}}
        {{/struct:py3_fields}}
        })

    @staticmethod
    cdef _create_FBTHRIFT_ONLY_DO_NOT_USE(shared_ptr[{{> types/current_module_cbindings}}.{{> types/c_struct }}] cpp_obj):
        __fbthrift_inst = <{{struct:name}}>{{struct:name}}.__new__({{struct:name}}{{#struct:exception?}}, (<bytes>deref(cpp_obj).what()).decode('utf-8'){{/struct:exception?}})
        __fbthrift_inst.{{> types/cpp_obj}} = cmove(cpp_obj)
        {{#struct:exception?}}
        _builtins.Exception.__init__(__fbthrift_inst, *(v for _, v in __fbthrift_inst))
        {{/struct:exception?}}
        return __fbthrift_inst

    {{#struct:py3_fields}}
    cdef inline {{field:py_name}}_impl(self):
        {{#field:optional?}}
        {{^field:hasDefaultValue?}}
        {{#field:has_ref_accessor?}}
        {{^field:terse?}}
        if not deref(self.{{> types/cpp_obj}}).{{field:py_name}}_ref().has_value():
            return None
        {{/field:terse?}}
        {{/field:has_ref_accessor?}}
        {{/field:hasDefaultValue?}}
        {{/field:optional?}}
        {{#field:type}}
        {{> types/cython_struct_getter}}

        {{/field:type}}

    @property
    def {{field:py_name}}(self):
        return self.{{field:py_name}}_impl()

    {{/struct:py3_fields}}

    def __hash__({{struct:name}} self):
        return super().__hash__()

    def __repr__({{struct:name}} self):
        return super().__repr__()

    def __str__({{struct:name}} self):
        {{^struct:exception?}}
        return super().__str__()
        {{/struct:exception?}}
        {{#struct:exception?}}
        {{#struct:exception_message?}}
        field = self.{{struct:exception_message}}
        if field is None:
            {{! optional field, stringify }}
            return str(field)
        return field
        {{/struct:exception_message?}}
        {{^struct:exception_message?}}
        return super().__str__()
        {{/struct:exception_message?}}
        {{/struct:exception?}}


{{/struct:union?}}{{!
}}{{#struct:union?}}


@__cython.auto_pickle(False)
@__cython.final
cdef class {{struct:name}}(thrift.py3.types.Union):
    __module__ = _fbthrift__module_name__
    Type = __{{struct:name}}Type

    def __init__(
        self, *{{#struct:py3_fields}},
        {{#field:type}}{{^type:structured?}}{{!
            }}{{#type:cythonTypeNoneable?}}{{!
                }}{{> types/cython_python_type}} {{!
            }}{{/type:cythonTypeNoneable?}}{{!
        }}{{/type:structured?}}{{/field:type}}{{!
            }}{{field:py_name}}=None{{!
        }}{{/struct:py3_fields}}
    ):
        {{#struct:py3_fields}}
        {{#field:type}}
        {{^type:container?}}
        {{^type:cythonTypeNoneable?}}
        {{^type:structured?}}
        if {{field:py_name}} is not None:
            if not isinstance({{field:py_name}}, {{> types/python_is_instance_type}}):
                raise TypeError(f'{{field:py_name}} is not a { {{> types/python_type}} !r}.')

        {{/type:structured?}}
        {{/type:cythonTypeNoneable?}}
        {{#type:integer?}}
            {{! inject cython int overflow checks }}
            {{field:py_name}} = <{{> types/cython_python_type}}> {{field:py_name}}

        {{/type:integer?}}
        {{#type:structured?}}
        {{field:py_name}} = _ensure_py3_or_raise({{field:py_name}}, "{{field:py_name}}", {{> types/python_is_instance_type}})

        {{/type:structured?}}
        {{/type:container?}}
        {{#type:is_container_of_struct?}}
        {{field:py_name}} = _ensure_py3_container_or_raise({{field:py_name}}, {{type:flat_name}})

        {{/type:is_container_of_struct?}}
        {{/field:type}}
        {{/struct:py3_fields}}
        self.{{> types/cpp_obj}} = __to_shared_ptr(cmove({{struct:name}}._make_instance(
          NULL,{{#struct:py3_fields}}
          {{field:py_name}},{{/struct:py3_fields}}
        )))
        self._initialize_py()

    @staticmethod
    def fromValue(value):
        if value is None:
            return {{struct:name}}()
        {{! We do this with strict types first, then we will do int to float conversion}}
        {{#struct:py3_fields}}
        {{#field:type}}
        if isinstance(value, {{> types/python_type}}):
            {{#type:number?}}
            if not isinstance(value, pbool):
                try:
                    {{#type:integer?}}
                    {{! Cython does OverflowError checking for us }}
                    <{{> types/cython_python_type}}> value
                    {{/type:integer?}}
                    {{#type:float?}}
                    {{! This will probably fail most of the time
                        if it does then when we try again to use floating point
                        below it will just accept the loss of precision,
                        or just maybe there is a double field comming up }}
                    if <{{> types/cython_python_type}}>value != value:
                        raise OverflowError
                    {{/type:float?}}
                    return {{struct:name}}({{field:py_name}}=value)
                except OverflowError:
                    pass
            {{/type:number?}}
            {{^type:number?}}
            return {{struct:name}}({{field:py_name}}=value)
            {{/type:number?}}
        {{/field:type}}
        {{/struct:py3_fields}}
        {{#struct:py3_fields}}
        {{#field:type}}
        {{#type:floating_point?}}
        if isinstance(value, {{> types/python_is_instance_type}}):
            try:
                <{{> types/cython_python_type}}> value
                return {{struct:name}}({{field:py_name}}=value)
            except OverflowError:
                pass
        {{/type:floating_point?}}
        {{/field:type}}
        {{/struct:py3_fields}}
        raise ValueError(f"Unable to derive correct union field for value: {value}")

    @staticmethod
    cdef unique_ptr[{{> types/current_module_cbindings}}.{{> types/c_struct }}] _make_instance(
        {{> types/current_module_cbindings}}.{{> types/c_struct }}* base_instance{{#struct:py3_fields}},
        {{#field:type}}{{!
            }}{{#type:cythonTypeNoneable?}}{{!
                }}{{> types/cython_python_type}} {{!
            }}{{/type:cythonTypeNoneable?}}{{!
            }}{{^type:cythonTypeNoneable?}}{{!
                }}object {{!
            }}{{/type:cythonTypeNoneable?}}{{!
            }}{{field:py_name}}{{!
        }}{{/field:type}}{{/struct:py3_fields}}
    ) except *:
        cdef unique_ptr[{{> types/current_module_cbindings}}.{{> types/c_struct }}] c_inst = make_unique[{{> types/current_module_cbindings}}.{{> types/c_struct }}]()
        cdef bint any_set = False
        {{#struct:py3_fields}}{{#field:type}}
        if {{field:py_name}} is not None:
            if any_set:
                raise TypeError("At most one field may be set when initializing a union")
            {{> types/cython_union_assign_field}}

            any_set = True
        {{/field:type}}{{/struct:py3_fields}}
        # in C++ you don't have to call move(), but this doesn't translate
        # into a C++ return statement, so you do here
        return cmove(c_inst)

    @staticmethod
    cdef _create_FBTHRIFT_ONLY_DO_NOT_USE(shared_ptr[{{> types/current_module_cbindings}}.{{> types/c_struct }}] cpp_obj):
        __fbthrift_inst = <{{struct:name}}>{{struct:name}}.__new__({{struct:name}})
        __fbthrift_inst.{{> types/cpp_obj}} = cmove(cpp_obj)
        __fbthrift_inst._initialize_py()
        return __fbthrift_inst

    {{#struct:py3_fields}}
    @property
    def {{field:py_name}}({{struct:name}} self not None):
        if self.type_int != {{field:id}}:
            {{! TODO: python 3.10 adds some more fields to AttributeError, those should be added here at some point }}
            raise AttributeError(f'Union contains a value of type {self.type.name}, not {{field:py_name}}')
        return self.value

    {{/struct:py3_fields}}

    def __hash__({{struct:name}} self):
        return  super().__hash__()

    @property
    def type({{struct:name}} self not None):
        if self.py_type is None:
            self.py_type = {{struct:name}}.Type(self.type_int)
        return self.py_type

    @property
    def value({{struct:name}} self not None):
        if self.py_value is not None or self.type_int == 0:
            return self.py_value
        {{#struct:py3_fields}}
        elif self.type_int == {{field:id}}:
            {{#field:type}}
            {{> types/cython_union_getter}}

            {{/field:type}}
        {{/struct:py3_fields}}
        return self.py_value

    {{! initialize python cache}}
    cdef _initialize_py({{struct:name}} self):
        self.py_type = None
        self.type_int = int(deref(self.{{> types/cpp_obj}}).getType())
        self.py_value = None

{{/struct:union?}}
    {{! Below are some things that are common to structs and unions: }}
    def __copy__({{struct:name}} self):
        return self{{! Thrift-py3 has immutable semantics}}
    {{#struct:cpp_noncomparable}}

    def __eq__({{struct:name}} self, other):
        return isinstance(other, {{struct:name}}) and self._fbthrift_noncomparable_eq(other)
    {{/struct:cpp_noncomparable}}
    {{^struct:is_struct_orderable?}}

    def __eq__({{struct:name}} self, other):
        if not isinstance(other, {{struct:name}}):
            return False
        return deref(self.{{> types/cpp_obj}}.get()) == deref((<{{struct:name}}>other).{{> types/cpp_obj}}.get())

    def __ne__({{struct:name}} self, other):
        if not isinstance(other, {{struct:name}}):
            return True
        return deref(self.{{> types/cpp_obj}}) != deref((<{{struct:name}}>other).{{> types/cpp_obj}})
    {{/struct:is_struct_orderable?}}
    {{^struct:cpp_noncomparable}}
    {{#struct:is_struct_orderable?}}

    def __richcmp__(self, other, int op):
        r = self._fbthrift_cmp_sametype(other, op)
        return __richcmp[{{> types/current_module_cbindings}}.{{> types/c_struct }}](
            self.{{> types/cpp_obj}},
            (<{{struct:name}}>other).{{> types/cpp_obj}},
            op,
        ) if r is None else r
    {{/struct:is_struct_orderable?}}
    {{/struct:cpp_noncomparable}}

    @staticmethod
    def __get_reflection__():
        return get_types_reflection().get_reflection__{{struct:name}}()

    @staticmethod
    def __get_metadata__():
        cdef __fbthrift_cThriftMetadata meta
        {{#struct:exception?}}
        {{> types/current_module_cbindings}}.ExceptionMetadata[{{> types/current_module_cbindings}}.{{> types/c_struct }}].gen(meta)
        {{/struct:exception?}}
        {{^struct:exception?}}
        {{> types/current_module_cbindings}}.StructMetadata[{{> types/current_module_cbindings}}.{{> types/c_struct }}].gen(meta)
        {{/struct:exception?}}
        return __MetadataBox.box(cmove(meta))

    @staticmethod
    def __get_thrift_name__():
        return "{{program:name}}.{{struct:name}}"

    {{#struct:has_hidden_fields?}}
    __fbthrift_field_name_list = [
        {{#struct:py3_fields}}
        '{{field:py_name}}',
        {{/struct:py3_fields}}
    ]

    @classmethod
    def _fbthrift_get_field_name_by_index(cls, idx):
        return cls.__fbthrift_field_name_list[idx]
    {{/struct:has_hidden_fields?}}
    {{^struct:has_hidden_fields?}}
    @classmethod
    def _fbthrift_get_field_name_by_index(cls, idx):
        return __sv_to_str(__get_field_name_by_index[{{> types/current_module_cbindings}}.{{> types/c_struct }}](idx))
    {{/struct:has_hidden_fields?}}

    @classmethod
    def _fbthrift_get_struct_size(cls):
        return {{struct:size}}

    cdef _fbthrift_iobuf.IOBuf _fbthrift_serialize({{struct:name}} self, __Protocol proto):
        cdef unique_ptr[_fbthrift_iobuf.cIOBuf] data
        with nogil:
            data = cmove(serializer.cserialize[{{> types/current_module_cbindings}}.{{> types/c_struct }}](self.{{> types/cpp_obj}}.get(), proto))
        return _fbthrift_iobuf.from_unique_ptr(cmove(data))

    cdef cuint32_t _fbthrift_deserialize({{struct:name}} self, const _fbthrift_iobuf.cIOBuf* buf, __Protocol proto) except? 0:
        cdef cuint32_t needed
        {{!
            This is a special case, we need to construct an empty _cpp_obj because
            thrift.py3.serializer.deserialize will just call __new__ to skip
            all of our runtime type checks. We do it like this because
            thrift.py3.serializer.deserialize does not have enough type information
            to call the staticmethod ._create_FBTHRIFT_ONLY_DO_NOT_USE()
        }}
        self.{{> types/cpp_obj}} = make_shared[{{> types/current_module_cbindings}}.{{> types/c_struct }}]()
        with nogil:
            needed = serializer.cdeserialize[{{> types/current_module_cbindings}}.{{> types/c_struct }}](buf, self.{{> types/cpp_obj}}.get(), proto)
        {{#struct:union?}}
        # clear cache reload since the underlying data's changed
        self._initialize_py()
        {{/struct:union?}}
        return needed

    {{#program:python_capi_converter?}}
    @staticmethod
    def from_python(object obj):
        cdef shared_ptr[{{> types/current_module_cbindings}}.{{> types/c_struct }}] cpp_obj = {{!
            }}__python_to_py3[{{> types/current_module_cbindings}}.{{> types/c_struct }}, _fbthrift__NamespaceTag](obj)
        return {{struct:name}}._create_FBTHRIFT_ONLY_DO_NOT_USE(cmove(cpp_obj))
    {{/program:python_capi_converter?}}

    def _to_python(self):
    {{#program:python_capi_converter?}}
        return __py3_to_python[{{> types/current_module_cbindings}}.{{> types/c_struct }}, {{!
            }}_fbthrift__NamespaceTag](self.{{> types/cpp_obj}})
    {{/program:python_capi_converter?}}
    {{^program:python_capi_converter?}}
        return thrift.python.converter.to_python_struct(
            _fbthrift_python_types.{{struct:name}},
            self,
        )
    {{/program:python_capi_converter?}}

    def _to_py3(self):
        return self

    def _to_py_deprecated(self):
        import thrift.util.converter
        py_deprecated_types = importlib.import_module("{{program:py_deprecated_module_path}}.ttypes")
        return thrift.util.converter.to_py_struct(py_deprecated_types.{{struct:name}}, self)

{{/program:filtered_structs}}
{{#program:containerTypes}}
{{> types/container_converter_impl }}

{{/program:containerTypes}}

{{#program:constants}}{{#constant:value}}
{{constant:name}} = {{> types/constant_value}}
{{/constant:value}}{{/program:constants}}
{{/program:inplace_migrate?}}
{{#program:filtered_typedefs}}
{{typedef:name}} = {{#typedef:asType}}{{> types/python_type}}{{/typedef:asType}}
{{/program:filtered_typedefs}}
{{#program:has_stream?}}

{{#program:stream_types}}
cdef class ClientBufferedStream__{{type:flat_name}}(ClientBufferedStream):

    @staticmethod
    cdef _fbthrift_create(cClientBufferedStream[{{> types/cython_cpp_type}}]& c_obj, __RpcOptions rpc_options):
        __fbthrift_inst = ClientBufferedStream__{{type:flat_name}}(rpc_options)
        __fbthrift_inst._gen = make_unique[cClientBufferedStreamWrapper[{{> types/cython_cpp_type}}]](c_obj)
        return __fbthrift_inst

    @staticmethod
    cdef void callback(
        cFollyTry[__cOptional[{{> types/cython_cpp_type}}]]&& result,
        PyObject* userdata,
    ) noexcept:
        cdef __cOptional[{{> types/cython_cpp_type}}] opt_val
        cdef {{> types/cython_cpp_type}} _value
        stream, pyfuture, rpc_options = <object> userdata
        if {{#program:stream_exceptions}}{{!
        }}result.hasException[{{> types/cython_cpp_type}}]():
            pyfuture.set_exception({{!
            }}{{#program:inplace_migrate?}}{{#type:structured}}{{!
                }}{{> types/python_type}}.from_python({{!
                }}{{type:capi_converter_path}}.{{struct:name}}_from_cpp({{!
                }}thrift.py3.exceptions.unwrap_exception[{{> types/cython_cpp_type}}](result.exception()))){{!
            }}{{/type:structured}}{{/program:inplace_migrate?}}{{^program:inplace_migrate?}}{{!
                }}{{> types/cython_python_type}}._create_FBTHRIFT_ONLY_DO_NOT_USE({{!
                }}thrift.py3.exceptions.try_make_shared_exception[{{> types/cython_cpp_type}}](result.exception())){{!
            }}{{/program:inplace_migrate?}}{{!
            }})
        elif {{/program:stream_exceptions}}{{!
        }}result.hasException():
            pyfuture.set_exception(
                thrift.python.exceptions.create_py_exception(result.exception(), <__RpcOptions>rpc_options)
            )
        else:
            opt_val = result.value()
            if opt_val.has_value():
                try:
                    _value = opt_val.value()
                    pyfuture.set_result({{> stream/cython_cpp_value_to_python_value}})
                except Exception as ex:
                    pyfuture.set_exception(ex.with_traceback(None))
            else:
                pyfuture.set_exception(StopAsyncIteration())

    def __anext__(self):
        __loop = asyncio.get_event_loop()
        __future = __loop.create_future()
        # to avoid "Future exception was never retrieved" error at SIGINT
        __future.add_done_callback(lambda x: x.exception())
        __userdata = (self, __future, self._rpc_options)
        bridgeCoroTaskWith[__cOptional[{{> types/cython_cpp_type}}]](
            self._executor,
            deref(self._gen).getNext(),
            ClientBufferedStream__{{type:flat_name}}.callback,
            <PyObject *>__userdata,
        )
        return asyncio.shield(__future)

cdef class ServerStream__{{type:flat_name}}(ServerStream):
    pass

{{/program:stream_types}}
{{#program:response_and_stream_functions}}{{#function:return_type}}
cdef class ResponseAndClientBufferedStream__{{> stream/response_class_name_suffix}}(ResponseAndClientBufferedStream):

    @staticmethod
    cdef _fbthrift_create(cResponseAndClientBufferedStream[{{!
            }}{{#function:stream_first_response_type}}{{> types/cython_cpp_type}}{{/function:stream_first_response_type}}, {{!
            }}{{#function:stream_elem_type}}{{> types/cython_cpp_type}}{{/function:stream_elem_type}}]& c_obj, __RpcOptions rpc_options):
        __fbthrift_inst = ResponseAndClientBufferedStream__{{> stream/response_class_name_suffix}}()
        __fbthrift_inst._stream = ClientBufferedStream__{{#function:stream_elem_type}}{{type:flat_name}}{{/function:stream_elem_type}}._fbthrift_create(
            c_obj.stream, rpc_options,
        )
{{#function:stream_first_response_type}}
        cdef {{> types/cython_cpp_type}} _value = c_obj.response
        __fbthrift_inst._response = {{> stream/cython_cpp_value_to_python_value}}
{{/function:stream_first_response_type}}
        return __fbthrift_inst

    def __iter__(self):
        yield self._response
        yield self._stream

cdef class ResponseAndServerStream__{{> stream/response_class_name_suffix}}(ResponseAndServerStream):
    pass

{{/function:return_type}}{{/program:response_and_stream_functions}}
{{/program:has_stream?}}
{{#program:inplace_migrate?}}{{#program:needs_container_converters?}}
{{#program:containerTypes}}
{{> types/container_converter_impl }}

{{/program:containerTypes}}
{{/program:needs_container_converters?}}{{/program:inplace_migrate?}}
{{/program:auto_migrate?}}
